/*
 * Box.cpp
 *
 *  Created on: 12.2.2013
 *      Author: Travis
 */

#include "Box.h"

namespace Octree
{

// first itteration
bool actualVisible = false;
bool actualVisibleEnt = false;

// info in header file
std::vector<Box*> visibleBoxes;

int rootNesting;

float lastMinValue = 0.0;

// set root nesting
void setRootNesting(int n)
{
	rootNesting = n;
}

// update scene when camera is mooving
void updateScene()
{
	visibleBoxes.clear();
	actualVisible = false;
	actualVisibleEnt = false;
}

Box::Box(int _start_x, int _start_y, int _size, int _nesting, float _step,
		Terrain::Terrain *_terrain, Water *_w)
{
	start_x = _start_x;
	start_y = _start_y;

	cube = NULL;

	MinMaxTerrain = glm::vec2(0.0);

	size = _size;

	actualVisible = false;
	actualVisibleEnt = false;

	//std::cout << "Box size: " << size << std::endl;

	nesting = _nesting;
	step = _step;

	terrain = _terrain;
	water = _w;

	vertex = normal = uv = element = isize = 0;
	vertexW = normalW = uvW = elementW = isizeW = 0;

	for (int i = 0; i < COUNT_OF_CHILDREN; i++)
		children[i] = NULL;

	// setup collisin cube
	setCube(0.0);

	// children boxes invasion
	if (nesting > 0)
	{
		// recursive block!!!!
		sexFunction();
	}
	else
	{

		World::countBoxes += 1;

		getTerrainVBO();
		getWaterVBO();
	}
}

Box::~Box()
{
	deleteBox();

	// clear last scene visible boxes
	visibleBoxes.clear();
}

void Box::getTerrainVBO()
{
	// create VBO and set ID in memory
	// generate new vertex array for this area
	// first create buffers in OGL engine
	terrain->createBufferObject(&vertex, &normal, &uv, &element);

	// set vectors in terrain class
	terrain->setVectors(start_x, start_y, size);

	terrain->fillBufferObject(vertex, normal, uv, element);

	isize = terrain->getIsize();
}

void Box::getWaterVBO()
{

	water->setVectors((float) (start_x) * terrain->get_step(),
			(float) (start_y) * terrain->get_step(),
			(float) size * terrain->get_step());
	water->indexing();

	// create VBO and set ID in memory
	// generate new vertex array for this area
	// first create buffers in OGL engine
	water->createBufferObject(&vertexW, &normalW, &uvW, &elementW);
	isizeW = water->fillBufferObject(vertexW, normalW, uvW, elementW);
}

void Box::deleteBox()
{
	std::cout << "Delete all children" << std::endl;

	if (nesting > 0)
	{
		for (int i = 0; i < COUNT_OF_CHILDREN; i++)
			children[i]->deleteBox();
	}
	else
	{
		std::cout << "Delete all VBO" << std::endl;
		glDeleteBuffers(1, &vertex);
		glDeleteBuffers(1, &normal);
		glDeleteBuffers(1, &uv);
		glDeleteBuffers(1, &element);

		if (cube != NULL)
			delete (cube);
	}

	for (int i = 0; i < COUNT_OF_CHILDREN; i++)
	{
		free(children[i]);
	}
}

float Box::drawVisible(GLuint shadowMap, int shaderType, bool drawWiredBox,
		bool waterVisible, bool terVisible, GLuint refTexture,
		GLuint refractionTexture)
{
	// not last run childrens
	float minBuf = 666.0;

	if (nesting > 0)
	{
		if (((World::world->CubeInFrustum(*cube)) || (shaderType == SHADER_SHADOW))
				&& (!actualVisible))
		{
			for (int i = 0; i < COUNT_OF_CHILDREN; i++)
			{
				float in = children[i]->drawVisible(shadowMap, shaderType, drawWiredBox,
						waterVisible, terVisible, refTexture, refractionTexture);

				// set buffer minimal value
				if (in < minBuf)
					minBuf = in;
			}
		}

		// ROOT block done last after all iterations
		if (nesting == rootNesting)
		{
			/*
			 if (!actualVisible)
			 for (unsigned int i = 0; i < visibleBoxes.size(); i++)
			 std::cout << "Visible boxes: " <<  visibleBoxes[i] << std::endl;*/

			if (actualVisible)
			{
				// scene is up to date, call last boxes
				for (unsigned int i = 0; i < visibleBoxes.size(); i++)
					visibleBoxes[i]->drawVisible(shadowMap, shaderType, drawWiredBox,
							waterVisible, terVisible, refTexture, refractionTexture);

				minBuf = lastMinValue;
			}
			else
				lastMinValue = minBuf;

			actualVisible = true;
		}
		//------------------------------------------

	}
	else
	{
		if ((World::world->CubeInFrustum(*cube)) || (shaderType == SHADER_SHADOW))
		{

			World::countBoxesVisible += 1;
			// last box

			if ((terVisible)
					&& ((shaderType == SHADER_CLIP_ABOVE)
							&& (MinMaxTerrain.x <= (water->getHigh() + WATER_OFFSET))
							|| ((shaderType == SHADER_CLIP_UNDER)
									&& (MinMaxTerrain.x
											>= (water->getHigh() - (WATER_OFFSET * 10.0))))))
			{
				terrain->draw(shadowMap, shaderType,
						glm::vec3(((float) start_x) * step, 0.0, ((float) start_y) * step),
						vertex, normal, uv, element, isize);
			}

			// draw water if it's visible
			if ((waterVisible)
					&& (MinMaxTerrain.x <= (water->getHigh() + WATER_OFFSET)))
			{
				water->draw(refTexture, refractionTexture, vertexW, normalW, uvW,
						elementW, isizeW);
			}

			// only for debug
			if (drawWiredBox)
				cube->drawCube();

			// this box is visible so store it's address
			// but only when scene isn't up to date
			if (!actualVisible)
				visibleBoxes.push_back(this);

			// return minimum of this box area of terrain
			return (float) MinMaxTerrain.x;
		}
	}

	return minBuf;
}

void Box::updateEntityYPos()
{
	for (unsigned int i = 0; i < entityID.size(); i++)
	{
		if (entityID[i] != NULL)
		{

			if (entityID[i]->getType() != OBJ_GRASS)
			{
				glm::vec3 pos = entityID[i]->getPos();

				pos.y = terrain->getInterYVal(glm::vec2(pos.x, pos.z));

				entityID[i]->setPos(pos);
			}
		}
	}
}

void Box::updateLocal(DataTypes::Cube updateLocality, glm::vec3 pos)
{
	if (nesting > 0)
	{
		if (updateLocality.cubeInCubeThisMoved(*cube, 0.0, pos))
		{
			setCube(0.0);

			for (int i = 0; i < COUNT_OF_CHILDREN; i++)
				children[i]->updateLocal(updateLocality, pos);

		}
	}
	else
	{
		//  last boxes
		// set vectors in terrain class
		terrain->calcNormals(start_x - 5, start_y - 5, size + 5);

		terrain->setVectors(start_x, start_y, size);

		terrain->fillBufferObject(vertex, normal, uv, element);

		isize = terrain->getIsize();

		updateEntityYPos();

		setCube(getMinMaxYEntity().y);
	}
}

void Box::setCube(float high)
{
	MinMaxTerrain = terrain->getLocalMinMax(start_x, start_y, size);

	float s = MinMaxTerrain.y - MinMaxTerrain.x;

	if (high > MinMaxTerrain.y)
		s = high - MinMaxTerrain.x;

	if (cube == NULL)
		cube = new DataTypes::Cube(
				glm::vec3((float) (start_x + (size / 2)), MinMaxTerrain.x + s / 2,
						(float) (start_y + (size / 2))), size, s);
	else
		cube = new (cube) DataTypes::Cube(
				glm::vec3((float) (start_x + (size / 2)), MinMaxTerrain.x + s / 2,
						(float) (start_y + (size / 2))), size, s);

}

void Box::sexFunction()
{

	children[0] = new Box(start_x, start_y, size / 2, nesting - 1, step, terrain,
			water);
	children[1] = new Box(start_x + (size / 2), start_y, size / 2, nesting - 1,
			step, terrain, water);
	children[2] = new Box(start_x + (size / 2), start_y + (size / 2), size / 2,
			nesting - 1, step, terrain, water);
	children[3] = new Box(start_x, start_y + (size / 2), size / 2, nesting - 1,
			step, terrain, water);
}

// inserting entity
void Box::insertEntity(Entity *insert)
{
	if (nesting > 0)
	{
		// continue only when entity is in current box and when it's not in other box
		if ((insert->CubeInEntity(*cube)) && (!insert->isInserted()))
			for (int i = 0; i < COUNT_OF_CHILDREN; i++)
				children[i]->insertEntity(insert);
	}
	else
	{
		if ((insert->CubeInEntity(*cube)) && (!insert->isInserted()))
		{
			// insert entity data to extern vector
			entityID.push_back(insert);

			// mark as inserted unit (ignore in other boxes)
			insert->setInserted(true);

			// setup box size
			setCube(insert->getHightOfBBox());
		}
	}
}

void Box::drawEntity(int shaderType)
{
	if (nesting > 0)
	{
		// visible part?

		if (World::world->CubeInFrustum(*cube))
			for (int i = 0; i < COUNT_OF_CHILDREN; i++)
				children[i]->drawEntity(shaderType);
	}
	else
	{
		if ((World::world->CubeInFrustum(*cube)) && (!actualVisibleEnt))
		{
			for (unsigned int i = 0; i < entityID.size(); i++)
			{
				entityID[i]->draw(shaderType);
			}

			//std::cout << "Box ma entit: " << entityID.size() << std::endl;
		}

		// ROOT block done last after all iterations
		if (nesting == rootNesting)
		{
			if (actualVisibleEnt)
			{
				// scene is up to date, call last boxes
				for (unsigned int i = 0; i < visibleBoxes.size(); i++)
					visibleBoxes[i]->drawEntity(shaderType);
			}

			actualVisibleEnt = true;
		}
		//------------------------------------------
	}

}

glm::vec2 Box::getMinMaxYEntity()
{
	glm::vec2 out;

	out.y = 0.0;
	out.x = 0.0;

	for (unsigned int i = 0; i < entityID.size(); i++)
	{
		if (out.x > entityID[i]->getHightOfBBox())
		{
			// min
			out.x = entityID[i]->getHightOfBBox();
		}
		else if (out.y < entityID[i]->getHightOfBBox())
		{
			// max
			out.y = entityID[i]->getHightOfBBox();
		}
	}

	return out;
}

}

